 /*******************************************************************************
  * Copyright (c) 2003, 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.framework.eventmgr;

 /**
  * This class manages a list of listeners.
  * Listeners may be added or removed as necessary.
  * @since 3.1
  */
 public class EventListeners {
     /**
      * The empty array singleton instance, returned by getListeners()
      * when size == 0.
      */
     private static final ListElement[] emptyArray = new ListElement[0];

     /**
      * The initial capacity of the list. Always >= 1.
      */
     private final int initialCapacity;

     /**
      * The list of elements. Initially <code>null</code> but initialized
      * to an array of size initialCapacity the first time an element is added.
      * Maintains invariants:
      * list != null IFF size != 0
      * list[size] == null
      * for all i < size: list[i] != null
      * Access to this field must be protected by a synchronized region.
      */
     private ListElement[] list = null;

     /**
      * The current number of elements.
      * Maintains invariant: 0 <= size <= list.length.
      * Access to this field must be protected by a synchronized region.
      */
     private int size = 0;

     /**
      * If true and about to modify the list,
      * then the list must be copied first.
      * Access to this field must be protected by a synchronized region.
      */
     private boolean copyOnWrite = false;

     /**
      * Creates a listener list with an initial capacity of 10.
      *
      */
     public EventListeners() {
         this(10);
     }

     /**
      * Creates a listener list with the given initial capacity.
      *
      * @param capacity The number of listeners which this list can initially
      * accept without growing its internal representation; must be at
      * least 1
      * @throws IllegalArgumentException If capacity is less than 1.
      */
     public EventListeners(int capacity) {
         if (capacity < 1)
             throw new IllegalArgumentException ();
         this.initialCapacity = capacity;
     }

     /**
      * Add a listener to the list.
      * If a listener object is already in the list, then it is replaced.
      *
      * @param listener This is the listener object to be added to the list.
      * @param listenerObject This is an optional listener-specific object.
      * This object will be passed to the EventDispatcher along with the listener
      * when the listener is to be called. This may be null
      * @throws IllegalArgumentException If listener is null.
      */
     public synchronized void addListener(Object listener, Object listenerObject) {
         if (listener == null) {
             throw new IllegalArgumentException ();
         }

         if (size == 0) {
             list = new ListElement[initialCapacity];
         }
         else {
             // copy array if necessary
 if (copyOnWrite) {
                 copyList(size);
                 copyOnWrite = false;
             }

             // check for duplicates using identity
 for (int i = 0; i < size; i++) {
                 if (list[i].primary == listener) {
                     list[i] = new ListElement(listener, listenerObject); /* use the most recent companion */
                     return;
                 }
             }

             // grow array if necessary
 // This wont recopy list if copy on write occured above since that
 // would have grown the list.
 if (size == list.length) {
                 copyList(size);
             }
         }

         list[size] = new ListElement(listener, listenerObject);
         size++;
     }

     /**
      * Remove a listener from the list.
      *
      * @param listener This is the listener object to be removed from the list.
      * @throws IllegalArgumentException If listener is null.
      */
     public synchronized void removeListener(Object listener) {
         if (listener == null) {
             throw new IllegalArgumentException ();
         }

         for (int i = 0; i < size; i++) {
             if (list[i].primary == listener) {
                 size--;
                 if (size == 0) {
                     list = null; /* invariant: list must be null iff size is zero */
                     return;
                 }
                 if (copyOnWrite) {
                     copyList(i);
                     copyOnWrite = false;
                 }
                 else {
                     System.arraycopy(list, i + 1, list, i, size - i);
                     list[size] = null; /* invariant: end of list must be null */
                 }
                 return;
             }
         }
     }

     /**
      * Remove all listeners from the list.
      */
     public synchronized void removeAllListeners() {
         /* invariant: list must be null iff size is zero */
         list = null;
         size = 0;
     }

     /**
      * Return the list of (listener, listenerObject) pairs.
      * Package private method.
      * The array may be longer than the number of pairs in the array.
      * The end of the pairs is signalled by a null element or
      * end of array.
      * This array must not be modified by anyone and should not be
      * exposed outside of this package.
      * To reduce memory allocations, the internal array is shared
      * with the rest of this package. However an array returned by this method
      * must not be modified in anyway.
      *
      * @return A shared array that must not be modified by anyone.
      */
     synchronized ListElement[] getListeners() {
         if (size == 0) {
             return emptyArray;
         }
         copyOnWrite = true;
         return list;
     }
     
     /**
      * Copy the array.
      * @param i Index of element to remove from array. Must be equal to size to
      * copy entire array.
      * @throws IndexOutOfBoundsException If i < 0 or i > size.
      */
     private void copyList(int i) {
         if (i > size) {
             throw new IndexOutOfBoundsException ();
         }
         int capacity = (size * 3) / 2 + 1;
         if (capacity < initialCapacity) {
             capacity = initialCapacity;
         }
         ListElement[] newList = new ListElement[capacity];
         System.arraycopy(list, 0, newList, 0, i);
         if (i < size) {
             System.arraycopy(list, i + 1, newList, i, size - i);
         }
         list = newList;
     }

     /**
      * ListElement is a package private class. This class
      * represents a primary object (e.g. listener) and its companion object.
      * ListElements are stored in EventListeners.
      * ListElements are immutable.
      */
     static class ListElement {
         /**
          * Primary object.
          */
         final Object primary;

         /**
          * Companion object.
          */
         final Object companion;

         /**
          * Constructor for ElementList element
          * @param primary Primary object in element. Used for uniqueness.
          * @param companion Companion object stored with primary object.
          */
         ListElement(final Object primary, final Object companion) {
             this.primary = primary;
             this.companion = companion;
         }
     }
 }

